#include <winsock2.h>
#include <ws2tcpip.h>
#include "pThreads/semaphore.h"
#include <vector>
#include "CSemaphore.h"

#include "CBasicPacket.h"
#include "CSearchRequestPacket.h" 
//#include "md52.h"
#include "CBlowfish.h"
#include "define.h"
#include "CConsole.h"


CSearchRequestPacket::CSearchRequestPacket(void)
{
	data=NULL;

	char keys[24]= {0x30, 0x73, 0x3D, 0x6D, 0x3C, 0x31, 0x49, 0x5A, 0x32, 0x7A, 0x42, 0x43, 0x63, 0x38, 0x7B, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
	memcpy(&key[0],&keys[0],24);

	memset(&packetData,0,sizeof(seaPacket));
	packetData.country=255; //255 = not set, 0=sandy, 1=bastok, 2=windy
	packetData.race=255; //255 = not set, 0=hume,1=....
	packetData.getActiveLS=false;
	packetData.partyId=0;
}

CSearchRequestPacket::~CSearchRequestPacket(void)
{

}

char* CSearchRequestPacket::getData(void)
{
	return data;
}
char* CSearchRequestPacket::getKey(void)
{
	return &key[0];
}

unsigned int CSearchRequestPacket::getSize(void)
{
	return size;
}

struct seaPacket* CSearchRequestPacket::getPacketData(void)
{
	return &packetData;
}

void CSearchRequestPacket::logPacket(void)
{
	FILE* outputFile;
	outputFile=NULL;
	outputFile=fopen("./data/searchPacket_workload_bin.txt","w+");

	if (outputFile!=NULL)
	{
		byte val;
		byte lineBreak=0;
		for(int i = 1; i <= data[0x10]; i++) 
		{
			val = data[0x10+i] ; 
			for(int t=128; t > 0; t = t/2) 
			{ 
				if((val & t) != 0) 
					fprintf(outputFile,"1");  
				if((val & t) == 0) 
					fprintf(outputFile,"0");  
			} 
			fprintf(outputFile," ");
			lineBreak++;
			if (lineBreak==8)
			{
				lineBreak=0;
				fprintf(outputFile,"\n");
			}
		}
		fclose(outputFile);
		outputFile=NULL;
	}

	outputFile=fopen("./data/searchPacket_workload.dat","wb+");
	if (outputFile!=NULL)
	{
		fwrite(&data[0x11],sizeof(char),data[0x10],outputFile);
		fclose(outputFile);
		outputFile=NULL;
	}

	outputFile=fopen("./data/searchPacket_full.dat","wb+");
	if (outputFile!=NULL)
	{
		fwrite(&data[0],sizeof(char),size,outputFile);
		fclose(outputFile);
		outputFile=NULL;
	}
}

void CSearchRequestPacket::receiveFromSocket(SOCKET *s)
{
	char receivedData[1024];
	memset(receivedData,0,1024);

	size = recv(*s,receivedData,1024,0);
	if (size == (unsigned long)-1) {
		return;
	}
	if (size!=*((unsigned long*)&receivedData[0]))
	{
		CConsole::outErr("Search packetsize wrong. Size %d should be %d.\n",size,*((unsigned long*)receivedData[0]));
		return;
	}

	if (data!=NULL)
		delete data;

	data=new char[size];
	memcpy(&data[0],&receivedData[0],size);

	//read key 1 from packet
	memcpy(key+16,data+size-4,4);

	//decipher Packet
	decipher();

	//parse the packet
	parsePacket();
}

void CSearchRequestPacket::checkHash(void)
{
	unsigned char packetHash[16];

	//generate Checksum
	int toHash=size; //whole packet
	toHash-=0x08;//-headersize
	toHash-=0x10;//-hashsize
	toHash-=0x04;//-keysize

	md5((unsigned char*)(&data[8]), packetHash, toHash);

	for(unsigned char i=0; i<16;i++)
	{
		if ((unsigned char)data[size-0x14+i]!=(unsigned char)packetHash[i])
		{
			CConsole::outErr("Search hash wrong byte %d: 0x%.2X should be 0x%.2x\n",i,packetHash[i],(unsigned char)data[size-0x14+i]);
		}
	}
}

void CSearchRequestPacket::decipher(void)
{
	unsigned long P[18];
	unsigned long S[4][256];

	unsigned char hash[16];

	md5((unsigned char*)(key), hash, 20);

	CBlowfish::init((char *)hash,16,P,S[0]);

	unsigned char tmp = (size-12)/4;
	tmp -= tmp%2;

	//decipher bytes 8 - (size-4)
	for(unsigned char i = 0; i < tmp; i += 2) {
		CBlowfish::decipher((unsigned long *)data+2+i, (unsigned long *)data+i+2+1,P,S[0]);
	}

	checkHash();

	memcpy(packetData.key2,data+(size-0x18),4);
	packetData.pSize = data[0x10];
}

void CSearchRequestPacket::parsePacket(void)
{
	unsigned short bitOffset=0;
	unsigned char entryType;
	unsigned char sortDescending;
	unsigned char isPresent;

	unsigned short workloadBits=packetData.pSize*8;
	CConsole::outDebOnly("Received a search packet with size %.8X byte",packetData.pSize);
	
	if (packetData.pSize==-1) //Leader of the Party list Request
	{
		//Set the partyId to be the search party callers target ID
		memcpy(&packetData.partyId,(data+0x11),sizeof(unsigned short));
		return;
	}
	else if (packetData.pSize==0) //Linkshell or Party list request
	{
		if ((short)data[0x14]==-1) //Check to see if this is a party list
		{
			memcpy(&packetData.partyId,(data+0x15),sizeof(unsigned short));
			return;
		}
		packetData.getActiveLS=true;
	} 
	while(bitOffset<workloadBits)
	{
		if ((bitOffset+5) >= workloadBits)
		{
			bitOffset=workloadBits;
			break;
		}

		entryType=(unsigned char)CBasicPacket::unpackBitsLE(&data[0x11],bitOffset,5);
		bitOffset+=5;

		if ((entryType!=SEARCH_FRIEND) &&
			(entryType!=SEARCH_LINKSHELL) &&
			(entryType!=SEARCH_COMMENT) &&
			(entryType!=SEARCH_FLAGS2))		
		{
			if ((bitOffset+3) >= workloadBits) //so 0000000 at the end does not get interpretet as name entry ...
			{
				bitOffset=workloadBits;
				break;
			}
			sortDescending=(unsigned char)CBasicPacket::unpackBitsLE(&data[0x11],bitOffset,1);
			bitOffset+=1;

			isPresent=(unsigned char)CBasicPacket::unpackBitsLE(&data[0x11],bitOffset,1);
			bitOffset+=1;
		}
		switch(entryType)
		{
			case SEARCH_NAME:
			{
				if (isPresent==0x1) //Name send
				{
					if ((bitOffset+5) >= workloadBits)
					{
						bitOffset=workloadBits;
						break;
					}
					packetData.nameLen = (unsigned char)CBasicPacket::unpackBitsLE(&data[0x11],bitOffset,5);
					packetData.name[packetData.nameLen]='\0';
					
					bitOffset+=5;

					for (unsigned char i=0;i<packetData.nameLen;i++)
					{
						packetData.name[i] = (char)CBasicPacket::unpackBitsLE(&data[0x11],bitOffset,7);
						bitOffset+=7;
					}	
					CConsole::outDebOnly("SEARCH::Name Entry Found. (%s).",packetData.name);
				}
				CConsole::outDebOnly("SEARCH::SortByName: %s.",(sortDescending==0x00)?"ascending":"descending");
				packetData.sortDescendingByName=sortDescending;
				break;
			}			
			case SEARCH_AREA: //Area Code Entry - 8 bit
			{
				if (isPresent==0x0) //no more Area entries
				{
					CConsole::outDebOnly("SEARCH::Area List End found.");
				}
				else // 8 Bit = 1 Byte per Area Code
				{
					packetData.areas[packetData.areaCount] = (unsigned char)CBasicPacket::unpackBitsLE(&data[0x11],bitOffset,8);
					packetData.areaCount++;
					bitOffset+=8;

					CConsole::outDebOnly("SEARCH::Area List Entry found(%2X)!",packetData.areas[packetData.areaCount-1]);
				}
				break;
			}
			case SEARCH_NATIONALITY: //Country - 2 bit
			{
				if (isPresent==0x1)
				{
					packetData.country = (unsigned char)CBasicPacket::unpackBitsLE(&data[0x11],bitOffset,2);
					bitOffset+=2;

					CConsole::outDebOnly("SEARCH::Nationality Entry found. (%2X) Sorting: (%s).",packetData.country,(sortDescending==0x00)?"ascending":"descending");
				}
				break;
			}
			case SEARCH_JOB: //Job - 5 bit
			{
				if (isPresent==0x1)
				{
					packetData.job = (unsigned char)CBasicPacket::unpackBitsLE(&data[0x11],bitOffset,5);
					bitOffset+=5;

					CConsole::outDebOnly("SEARCH::Job Entry found. (%2X) Sorting: (%s).",packetData.job,(sortDescending==0x00)?"ascending":"descending");
				}
				packetData.sortDescendingByJob=sortDescending;
				CConsole::outDebOnly("Search::SortByJob: %s.",(sortDescending==0x00)?"ascending":"descending");
				break;
			}
			case SEARCH_LEVEL: //Level- 16 bit
			{
				if (isPresent==0x1)
				{
					packetData.fromLvl = (unsigned char)CBasicPacket::unpackBitsLE(&data[0x11],bitOffset,8);
					bitOffset+=8;
					packetData.toLvl = (unsigned char)CBasicPacket::unpackBitsLE(&data[0x11],bitOffset,8);
					bitOffset+=8;

					CConsole::outDebOnly("SEARCH::Level Entry found. (%d - %d) Sorting: (%s).",packetData.fromLvl,packetData.toLvl,(sortDescending==0x00)?"ascending":"descending");
				}
				packetData.sortDescendingByLevel=sortDescending;
				CConsole::outDebOnly("Search::SortByLevel: %s.",(sortDescending==0x00)?"ascending":"descending");
				break;
			}
			case SEARCH_RACE: //Race - 4 bit
			{
				if (isPresent==0x1)
				{
					packetData.race = (unsigned char)CBasicPacket::unpackBitsLE(&data[0x11],bitOffset,4);
					bitOffset+=4;
					CConsole::outDebOnly("SEARCH::Race Entry found. (%2X) Sorting: (%s).",packetData.race,(sortDescending==0x00)?"ascending":"descending");
				}
				CConsole::outDebOnly("Search::SortByRace: %s.",(sortDescending==0x00)?"ascending":"descending");
				packetData.sortDescendingByRace=sortDescending;
				break;
			}
			case SEARCH_RANK: //Rank - 2 byte
			{
				if (isPresent==0x1)
				{
					packetData.fromRank = (unsigned char)CBasicPacket::unpackBitsLE(&data[0x11],bitOffset,8);
					bitOffset+=8;
					packetData.toRank = (unsigned char)CBasicPacket::unpackBitsLE(&data[0x11],bitOffset,8);
					bitOffset+=8;

					CConsole::outDebOnly("SEARCH::Rank Entry found. (%d - %d) Sorting: (%s).",packetData.fromRank,packetData.toRank,(sortDescending==0x00)?"ascending":"descending");
				}
				CConsole::outDebOnly("Search::SortByRank: %s.",(sortDescending==0x00)?"ascending":"descending");
				packetData.sortDescendingByRank=sortDescending;
				break;
			}
			case SEARCH_COMMENT: //4 Byte
			{
				packetData.comment = (unsigned int)CBasicPacket::unpackBitsLE(&data[0x11],bitOffset,32); 
				bitOffset+=32;

				CConsole::outDebOnly("SEARCH::Comment Entry found. (%8X).",packetData.comment);
				break;
			}
			//the following 4 Entries were generated with /sea (ballista|friend|linkshell|away|inv) 
			//so they may be off
			case SEARCH_LINKSHELL: // 4 Byte
			{
				packetData.lsId= (unsigned int)CBasicPacket::unpackBitsLE(&data[0x11],bitOffset,32);
				bitOffset+=32;

				CConsole::outDebOnly("SEARCH::Linkshell Entry found. Value: %.8X",packetData.lsId);
				break;
			}
			case SEARCH_FRIEND: // Friend Packet, 0 byte
			{
				CConsole::outDebOnly("SEARCH::Friend Entry found.");
				break;
			}
			case SEARCH_FLAGS1: // Flag Entry #1, 2 byte, 
			{
				if (isPresent==0x1)
				{
					packetData.flags1 = (unsigned short)CBasicPacket::unpackBitsLE(&data[0x11],bitOffset,16);
					bitOffset+=16;

					CConsole::outDebOnly("SEARCH::Flag Entry #1 (%.4X) found. Sorting: (%s).",packetData.flags1,(sortDescending==0x00)?"ascending":"descending");
				}
				CConsole::outDebOnly("Search::SortByFlags: %s",(sortDescending==0x00)?"ascending":"descending");
				packetData.sortDescendingByFlags=sortDescending;
				break;
			}
			case SEARCH_FLAGS2: // Flag Entry #2 - 4 byte
			{
				unsigned int flags=(unsigned int)CBasicPacket::unpackBitsLE(&data[0x11],bitOffset,32);

				bitOffset+=32;

				if ((flags&0xFFFF)!=(packetData.flags1))
				{
					CConsole::outDebOnly("SEARCH::Flag mismatch: %.8X != %.8X",flags,packetData.flags1&0xFFFF);
				}
				packetData.flags2=flags;
				CConsole::outDebOnly("SEARCH::Flag Entry #2 (%.8X) found.",packetData.flags2);
				break;
			}
			default:
			{
				CConsole::outDeb("SEARCH::Unknown Search Param %.2X!",entryType);
				//outputPacket=true;
				break;
			}
		}
	}
}